package com.ctsi.ssdc.controller;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import com.ctsi.ssdc.admin.cache.LoginCache;
import com.ctsi.ssdc.util.RedisUtil;
import com.zdww.biyi.component.sdk.aop.BeanExposeMethodAble;

import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.ctsi.ssdc.admin.service.CscpMenusService;
import com.ctsi.ssdc.admin.service.CscpUserService;
import com.ctsi.ssdc.admin.service.UserService;
import com.ctsi.ssdc.captcha.BiyiCaptcha;
import com.ctsi.ssdc.exception.BadRequestAlertException;
import com.ctsi.ssdc.model.UserForm;
import com.ctsi.ssdc.security.CasAuthenticationToken;
import com.ctsi.ssdc.security.SecurityUtils;
import com.ctsi.ssdc.security.UserLoginValidator;
import com.ctsi.ssdc.security.jwt.JWTConfigurer;
import com.ctsi.ssdc.security.jwt.TokenProvider;
import com.ctsi.ssdc.util.RSAUtil;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;

import static com.ctsi.ssdc.admin.consts.ComponentConstant.ADMIN;
import static com.ctsi.ssdc.admin.consts.ComponentConstant.METHOD;

/**
 * Controller to authenticate users.
 */
@RestController
@RequestMapping("/api/system")
public class UserJwtController {

	private final Logger logger = LoggerFactory.getLogger(UserJwtController.class);

	private static final String UNAUTHORIZED = "Unauthorized";

	private final UserService userService;

	@Autowired(required = false)
	UserLoginValidator userLoginValidator;

	@Autowired
	CscpUserService cscpUserService;
	
	private final CscpMenusService cscpMenusService;

	@Value("${ctsi.RSA-prikey:}")
	private String rsaPrikey = "";

	@Value("${ctsi.login.bad-password-attempts:5}")
	private int badPasswordAttempts ;

	@Value("${ctsi.login.lockout-time:3600}")
	private int lockoutTime;

	private final TokenProvider tokenProvider;

	private final AuthenticationManager authenticationManager;

//	@Resource(name = "redisUtil")
//	private RedisUtil redisUtil;

	@Resource(name = "${ctsi.login.cache:guavaLoginCache}")
	LoginCache loginCache;

	@Autowired
	PasswordEncoder passwordEncoder;

	public UserJwtController(TokenProvider tokenProvider, AuthenticationManager authenticationManager, CscpMenusService cscpMenusService, UserService userService) {
		this.tokenProvider = tokenProvider;
		this.authenticationManager = authenticationManager;
		this.cscpMenusService = cscpMenusService;
		this.userService = userService;
	}

	@BeanExposeMethodAble(component = ADMIN,method = "")
	@ApiImplicitParams({
		 @ApiImplicitParam(name="biyiCaptchaKey", value="验证码key", paramType="header", dataType="String", defaultValue=""),
		 @ApiImplicitParam(name="biyiCaptcha", value="验证码", paramType="header", dataType="String", defaultValue="{\"code\":\"1234\"}")
	})
//	@BiyiCaptcha(rule = "defaultRule", service = "digitalCaptchaService")
	@PostMapping(value = "/login")
	public ResponseEntity<JwtToken> authorize(HttpServletRequest request, @RequestBody UserForm user) {

		String userName = user.getUsername();
		//前端使用公钥进行加密，服务端私钥解密,私钥为空不解密
		String password = decryptPassword(user.getPassword());
		boolean rememberMe = user.getRememberme() == 1;

		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("loginFailed:");
		stringBuffer.append(userName);
		String countKey = stringBuffer.toString();
		Integer countKeyValue = null;
		if(loginCache!=null){
			countKeyValue = (Integer) loginCache.get(countKey);
			if(countKeyValue!=null && countKeyValue==badPasswordAttempts){

				long expire = loginCache.getExpire(countKey);
				return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new JwtToken((int) expire,0));
			}
		}

		String jwt;

		if (!cscpUserService.existByUsername(userName)) {
			throw new BadRequestAlertException("userName is not exist", "username", "notexist");
		}

		// 自定义验证
		if (userLoginValidator != null) {
			userLoginValidator.validate(user);
		}
		// security
		UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName,
				password);
		Authentication authentication = null;
		try {
			authentication = this.authenticationManager.authenticate(authenticationToken);
		} catch (AuthenticationException e) {
			if(loginCache!=null){
				int countKeyLeft  = -1;
				if(countKeyValue==null){
					loginCache.put(countKey,1);//lockoutTime /6
					countKeyLeft = badPasswordAttempts-1;
				}else if(countKeyValue<badPasswordAttempts){
					loginCache.put(countKey,countKeyValue+1);
					countKeyLeft = badPasswordAttempts-1-countKeyValue;
					if(countKeyLeft==0){
						loginCache.put(countKey,badPasswordAttempts);
						return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new JwtToken(lockoutTime,countKeyLeft));
					}
				}
				return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new JwtToken(countKeyLeft));
			}
			throw e;
		}
		//登陆成功，删除countKey
		if(loginCache!=null && countKeyValue!=null){
			loginCache.delete(countKey);
		}
		SecurityContextHolder.getContext().setAuthentication(authentication);
		jwt = tokenProvider.createToken(authentication, rememberMe);

		SecurityUtils.getOptionalCurrentUserId().map(userId -> {
			userService.updateUserDetailForLogin(String.valueOf(userId));
			return null;
		});

		HttpHeaders httpHeaders = new HttpHeaders();
		String token = JWTConfigurer.AUTHORIZATION_BEARER + jwt;

		int status = cscpUserService.passwordNeedChange(SecurityUtils.getCurrentUserId(),password);
		if(status==0){
			httpHeaders.add(JWTConfigurer.AUTHORIZATION_HEADER, token);
			return new ResponseEntity<>(new JwtToken(token,status), httpHeaders, HttpStatus.OK);
		}else{
			return new ResponseEntity<>(new JwtToken(token,status), httpHeaders, HttpStatus.OK);
		}


	}

	@GetMapping(value = "/refreshToken")
	@BeanExposeMethodAble(component = ADMIN,method = METHOD)
	public ResponseEntity<JwtToken> refreshToken(@RequestParam int rememberme) {

		boolean rememberMe = rememberme == 1;

		List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
		cscpMenusService.findByUserId(SecurityUtils.getCurrentUserId()).forEach(menu -> {

			if (StringUtils.isNotEmpty(menu.getPermissionCode())) {
				grantedAuthorities.add(new SimpleGrantedAuthority(menu.getPermissionCode()));
			}

		});

		Authentication currentAuth = SecurityContextHolder.getContext().getAuthentication();
		UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(
				currentAuth.getPrincipal(), currentAuth.getCredentials(), grantedAuthorities);

		String jwt = tokenProvider.createToken(newAuthentication, rememberMe);

		HttpHeaders httpHeaders = new HttpHeaders();
		String token = JWTConfigurer.AUTHORIZATION_BEARER + jwt;

		httpHeaders.add(JWTConfigurer.AUTHORIZATION_HEADER, token);
		return new ResponseEntity<>(new JwtToken(token), httpHeaders, HttpStatus.OK);

	}

	@PostMapping(value = "/loginByCas")
	@BeanExposeMethodAble(component = ADMIN,method = METHOD)
	public ResponseEntity<JwtToken> loginByCas(@RequestParam String serviceUrl, @RequestParam String ticket) {

		boolean rememberMe = false;

		CasAuthenticationToken authenticationToken = new CasAuthenticationToken(serviceUrl, ticket, null);

		Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
		SecurityContextHolder.getContext().setAuthentication(authentication);

		String jwt = tokenProvider.createToken(authentication, rememberMe);

		SecurityUtils.getOptionalCurrentUserId().map(userId -> {
			userService.updateUserDetailForLogin(String.valueOf(userId));
			return null;
		});

		HttpHeaders httpHeaders = new HttpHeaders();
		String token = JWTConfigurer.AUTHORIZATION_BEARER + jwt;

		httpHeaders.add(JWTConfigurer.AUTHORIZATION_HEADER, token);
		return new ResponseEntity<>(new JwtToken(token), httpHeaders, HttpStatus.OK);
	}

	/**
	 * 解密
	 * 
	 * @param ciphertext
	 *            密码的密文
	 * @return
	 */
	private String decryptPassword(String ciphertext) {
		String password = ciphertext;
		try {
			if (StringUtils.isNotBlank(rsaPrikey)) {
				password = new String(
						RSAUtil.decryptPri(Base64.decodeBase64(ciphertext), Base64.decodeBase64(rsaPrikey)));
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return password;
	}

	/**
	 * Object to return as body in JWT Authentication.
	 */
	public static class JwtToken {

		private String token;



		//
		private int passwordStatus;

		private int lockoutTime;

		private int attempts;
		JwtToken(String token) {
			this.token = token;
		}


		public JwtToken(String token, int passwordStatus) {
			this.token = token;
			this.passwordStatus = passwordStatus;
		}

		public JwtToken(int lockoutTime, int attempts) {
			this.lockoutTime = lockoutTime;
			this.attempts = attempts;
		}

		public JwtToken(int attempts) {
			this.attempts = attempts;
		}

		@JsonProperty("token")
		String getToken() {
			return token;
		}

		void setToken(String token) {
			this.token = token;
		}

		@JsonProperty("passwordStatus")
		int getPasswordStatus() {
			return passwordStatus;
		}

		void setPasswordStatus(int passwordStatus) {
			this.passwordStatus = passwordStatus;
		}

		@JsonProperty("lockoutTime")
		public int getLockoutTime() {
			return lockoutTime;
		}

		public void setLockoutTime(int lockoutTime) {
			this.lockoutTime = lockoutTime;
		}

		@JsonProperty("attempts")
		public int getAttempts() {
			return attempts;
		}

		public void setAttempts(int attempts) {
			this.attempts = attempts;
		}
	}

}
